#include <QApplication>
#include <QTabBar>
#include <QMessageBox>
#include <QFileDialog>
#include <QTimer>
#include <QDateTime>
#include <QMenu>
#include <QCursor>
#include <QClipboard>
#include <QProcess>
#include <Configuration.h>
#include <Utils.h>
#include <Resources.h>
#include <Constants.h>
#include <MessageServices.h>
#include <ActionManager.h>
#include <FileSystemHandler.h>
#include "XMLCoreWindow.h"
#include "XMLCoreEditor.h"
#include "XMLSyntax/XMLSyntaxChecker.h"

namespace Domy {

XMLCoreWindow::XMLCoreWindow(ActionEnableCtrl ctrl, QWidget *parent)
    : QTabWidget(parent),
      WindowInterface(ctrl),
      m_syntax_timer(new QTimer(this))
{
    this->setAttribute(Qt::WA_StyledBackground);
    this->setTabShape(QTabWidget::Rounded);
    this->setTabsClosable(true);
    this->setMovable(true);
    this->setElideMode(Qt::ElideMiddle);
    this->tabBar()->setObjectName("XMLCoreWindow");
    this->setStyleSheet(QString("QTabBar#%1::tab { width: 200px; }")
                        .arg(this->tabBar()->objectName()));
    this->tabBar()->setContextMenuPolicy(Qt::CustomContextMenu);

    this->setDocumentActionEnable(false);
    m_act_ctrl(ActKey_Undo, false);
    m_act_ctrl(ActKey_Redo, false);

    connect(this, SIGNAL(currentChanged(int)), this, SLOT(slotOnCurrentIndexChanged(int)));
    connect(this, SIGNAL(tabCloseRequested(int)), this, SLOT(slotOnTabCloseRequested(int)));
    connect(this->tabBar(), SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(slotOnTabBarMenuRequested(const QPoint &)));

    m_syntax_timer->setInterval(1000);
    connect(m_syntax_timer, SIGNAL(timeout()), this, SLOT(slotOnSyntaxCheckTimeout()));
}

XMLCoreWindow::~XMLCoreWindow()
{

}

void XMLCoreWindow::newDocument()
{
    this->newDocument("");

    m_syntax_timer->start();
}

bool XMLCoreWindow::openDocument(const QString &file_path)
{
    bool rslt = this->newDocument(file_path);
    if (rslt) {
        m_syntax_timer->start();
    }
    return rslt;
}

bool XMLCoreWindow::closeCurrentDocument()
{
    return this->closeDocument(this->currentIndex());
}

bool XMLCoreWindow::closeAllDocuments()
{
    bool rslt = true;
    for (int i = this->count() - 1; i >= 0; --i) {
        rslt &= this->closeDocument(i);
    }

    return rslt;
}

FileSavedReturnCode XMLCoreWindow::saveCurrentDocument()
{
    XMLCoreEditor *editor = dynamic_cast<XMLCoreEditor *>(this->currentWidget());

    if (!editor) {
        return FileSavedCode_NoChanged;
    }

    QString error;
    FileSavedReturnCode code = this->saveDocument(editor, &error);

    if (code == FileSavedCode_Failed) {
        QMessageBox::critical(this, tr("Save File"),
                              tr("The file(\"%1\") saved unsuccessfully ! Reason: %2 !")
                              .arg(editor->getFilePath()).arg(error),
                              QMessageBox::Ok);
    }
    updateCurrentDocumentChangeState();
    return code;
}

FileSavedReturnCode XMLCoreWindow::saveAsCurrentDocument(const QString &file_path)
{
    if (file_path.isEmpty()) {
        return FileSavedCode_NoFilePath;
    }

    XMLCoreEditor *editor = dynamic_cast<XMLCoreEditor *>(this->currentWidget());

    if (!editor) {
        return  FileSavedCode_NoChanged;
    }

    QString error;
    if (editor->saveAs(file_path, &error)) {
        return FileSavedCode_Suc;
    }
    else {
        QMessageBox::critical(this, tr("Save As"),
                              tr("The file(\"%1\") saved unsuccessfully ! Reason: %2 !")
                              .arg(editor->getFilePath().arg(error)),
                              QMessageBox::Ok);
        return FileSavedCode_Failed;
    }
}

bool XMLCoreWindow::saveAllDocuments()
{
    QString error;
    for (int i = 0; i < this->count(); ++i) {
        XMLCoreEditor *editor = dynamic_cast<XMLCoreEditor *>(this->widget(i));

        QString err;
        if (this->saveDocument(editor, &err) < 0) {
            error.append(QString("The file(\"%1\") saved unsuccessfully ! Reason: %2 !")
                         .arg(editor->getFilePath()).arg(err));
            error.append('\n');
        }
    }

    if (!error.isEmpty()) {
        QMessageBox::critical(this, tr("Save All Files"), error, QMessageBox::Ok, QMessageBox::Ok);
    }

    updateAllDocumentsChangeState();

    return error.isEmpty();
}

void XMLCoreWindow::doAction(ActionKey key)
{
    XMLCoreEditor *editor = dynamic_cast<XMLCoreEditor *>(this->currentWidget());
    if (!editor) {
        return;
    }
    editor->doEditAction(key);
}

void XMLCoreWindow::updateSettings()
{
    XMLCoreEditor *editor = Q_NULLPTR;
    for (int i = 0; i < this->count(); ++i) {
        editor = dynamic_cast<XMLCoreEditor *>(this->widget(i));
        if (editor) {
            editor->updateSettings();
        }
    }
}

void XMLCoreWindow::slotOnCurrentIndexChanged(int index)
{
    Q_UNUSED(index);

    XMLCoreEditor *editor = dynamic_cast<XMLCoreEditor *>(this->currentWidget());
    if (editor) {
        m_act_ctrl(ActKey_Undo, editor->isUndoEnable());
        m_act_ctrl(ActKey_Redo, editor->isRedoEnable());
    }

    m_syntax_timer->start();
}

void XMLCoreWindow::slotOnTabCloseRequested(int index)
{
    closeDocument(index);
}

void XMLCoreWindow::slotOnContentTextChanged()
{
    m_syntax_timer->stop();
    m_syntax_timer->start();
}

void XMLCoreWindow::slotOnContentModificationChanged()
{
    updateCurrentDocumentChangeState();
}

void XMLCoreWindow::slotOnFilePathChanged()
{
    XMLCoreEditor *editor = dynamic_cast<XMLCoreEditor *>(sender());

    QString file_path = editor->getFilePath();
    QFileInfo fi(file_path);
    QString new_title = fi.fileName();

    int index = this->indexOf(editor);
    QString title = this->tabText(index);
    if (title.contains("*")) {
        new_title.append("*");
    }

    this->setTabText(index, new_title);
    this->setTabToolTip(index, file_path);

    emit signalNewDocumentFilePathChanged(file_path);
}

void XMLCoreWindow::slotOnSyntaxCheckTimeout()
{
    this->checkSyntax();

    m_syntax_timer->stop();
}

void XMLCoreWindow::slotOnEditorUndoAvailable(bool available)
{
    m_act_ctrl(ActKey_Undo, available);
}

void XMLCoreWindow::slotOnEditorRedoAvailable(bool available)
{
    m_act_ctrl(ActKey_Redo, available);
}

void XMLCoreWindow::slotOnTabBarMenuRequested(const QPoint &pos)
{
    int index = this->tabBar()->tabAt(pos);
    if (index < 0) {
        return;
    }

    XMLCoreEditor *editor = dynamic_cast<XMLCoreEditor *>(this->widget(index));
    if (!editor) {
        return;
    }

    this->setCurrentIndex(index);

    QAction *act_cp_full_path = Q_NULLPTR;
    QAction *act_cp_file_name = Q_NULLPTR;
    QAction *act_save = Q_NULLPTR;
    QAction *act_save_as = Q_NULLPTR;
    QAction *act_close = Q_NULLPTR;
    QAction *act_explorer = Q_NULLPTR;

    QString file_name = editor->getFileName();

    QMenu menu;

    if (file_name.isEmpty()) {
        act_save = menu.addAction(GET_ICON("save"), tr("Save"));
        act_save_as = menu.addAction(GET_ICON("save_as"), tr("Save As ..."));

        menu.addSeparator();

        act_close = menu.addAction(GET_ICON("close_file"), tr("Close"));
    }
    else {
        act_cp_full_path = menu.addAction(GET_ICON("copy_link"), tr("Copy Full Path"));
        act_cp_file_name = menu.addAction(GET_ICON("copy_link"), tr("Copy File Name"));

        menu.addSeparator();

        act_save = menu.addAction(GET_ICON("save"), tr("Save \"%1\"").arg(file_name));
        act_save_as = menu.addAction(GET_ICON("save_as"), tr("Save \"%1\" As ...").arg(file_name));

        menu.addSeparator();

        act_close = menu.addAction(GET_ICON("close_file"), tr("Close \"%1\"").arg(file_name));

        menu.addSeparator();
        act_explorer = menu.addAction(GET_ICON("show_in_explorer"), tr("Show \"%1\" in Explorer").arg(file_name));
    }

    QAction *act = menu.exec(QCursor::pos());
    if (!act) {
        return;
    }

    if (act == act_cp_full_path) {
        // copy full path
        qApp->clipboard()->setText(editor->getFilePath());
    }
    else if (act == act_cp_file_name) {
        // copy file name
        qApp->clipboard()->setText(file_name);
    }
    else if (act == act_save) {
        // save
        domy_act->triggerAction(ActKey_SaveOne);
    }
    else if (act == act_save_as) {
        // save as
        domy_act->triggerAction(ActKey_SaveAs);
    }
    else if (act == act_close) {
        // close
        domy_act->triggerAction(ActKey_CloseFile);
    }
    else if (act == act_explorer) {
        // show in explorer
        FileSystemHandler::showInExplorer(editor->getFilePath());
    }
    else {

    }
}

bool XMLCoreWindow::newDocument(const QString &file_path)
{
    if (!file_path.isEmpty()) {
        int index = 0;
        bool is_found = false;
        XMLCoreEditor *editor = Q_NULLPTR;
        for (; index < this->count(); ++index) {
            editor = dynamic_cast<XMLCoreEditor *>(this->widget(index));
            if (file_path.compare(editor->getFilePath(), Qt::CaseInsensitive) == 0) {
                is_found = true;
                break;
            }
        }

        if (is_found) {
            this->setCurrentIndex(index);

            this->setDocumentActionEnable(true);

            return true;
        }
    }

    XMLCoreEditor *editor = new XMLCoreEditor(this);

    if (!editor->openFile(file_path)) {
        delete editor;
        editor = Q_NULLPTR;

        return false;
    }

    connect(editor, SIGNAL(signalContentModificationChanged()), this, SLOT(slotOnContentModificationChanged()));
    connect(editor, SIGNAL(signalContentTextChanged()), this, SLOT(slotOnContentTextChanged()));
    connect(editor, SIGNAL(signalFilePathChanged()), this, SLOT(slotOnFilePathChanged()));
    connect(editor, SIGNAL(signalUndoAvailable(bool)), this, SLOT(slotOnEditorUndoAvailable(bool)));
    connect(editor, SIGNAL(signalRedoAvailable(bool)), this, SLOT(slotOnEditorRedoAvailable(bool)));

    QString file_name = "untitled";
    if (!file_path.isEmpty()) {
        QFileInfo fi(file_path);
        file_name = fi.fileName();
    }

    int index = this->addTab(editor, file_name);

    this->setTabIcon(index, GET_ICON("xml_document"));

    this->setTabToolTip(index, file_path);

    this->setCurrentIndex(index);

    this->setDocumentActionEnable(true);

    return true;
}

FileSavedReturnCode XMLCoreWindow::saveDocument(XMLCoreEditor *editor, QString * const error)
{
    if (editor->isChanged()) {
        if (editor->getFilePath().isEmpty()) {
            QString file_path = QFileDialog::getSaveFileName(this, tr("Save File"),
                                                             domy_config->getLastOpenDirPath(), "");
            if (file_path.isEmpty()) {
                return FileSavedCode_NoFilePath;
            }
            else {
                editor->setFilePath(file_path);
                return (editor->save(error) ? FileSavedCode_Suc : FileSavedCode_Failed);
            }
        }
        else {
            return (editor->save(error) ? FileSavedCode_Suc : FileSavedCode_Failed);
        }
    }
    else {
        return FileSavedCode_NoChanged;
    }
}

bool XMLCoreWindow::closeDocument(int index)
{
    XMLCoreEditor *editor = dynamic_cast<XMLCoreEditor *>(this->widget(index));

    if (!editor) {
        return false;
    }

    auto save_on_close = [&](XMLCoreEditor *editor)->bool {
        QString error;
        if (editor->save(&error)) {
            return true;
        }
        else {
            QMessageBox::critical(this, tr("Save File"),
                                  tr("The file(\"%1\") saved unsuccessfully ! Reason: %2 !")
                                  .arg(editor->getFilePath()).arg(error),
                                  QMessageBox::Ok, QMessageBox::Ok);
            return false;
        }
    };

    bool is_close = false;

    if (editor->isChanged()) {
        QMessageBox::Button btn = QMessageBox::question(this, tr("Save File"), tr("The current file has changed. Do you want to save it?"),
                                                        QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
        if (btn == QMessageBox::Yes) {
            if (editor->getFilePath().isEmpty()) {
                QString file_path = QFileDialog::getSaveFileName(this, tr("Save File"),
                                                                 domy_config->getLastOpenDirPath(), "");
                if (file_path.isEmpty()) {
                    return false;
                }

                editor->setFilePath(file_path);
            }

            is_close = save_on_close(editor);
        }
        else if (btn == QMessageBox::No) {
            is_close = true;
        }
        else {
            is_close = false;
        }
    }
    else {
        is_close = true;
    }

    if (is_close) {
        this->removeTab(index);

        delete editor;
        editor = Q_NULLPTR;
    }

    if (this->count() == 0) {
        this->setDocumentActionEnable(false);
    }

    return is_close;
}

void XMLCoreWindow::updateDocumentChangeState(int index)
{
    XMLCoreEditor *editor = dynamic_cast<XMLCoreEditor *>(this->widget(index));
    if (!editor) {
        return;
    }

    QString title = this->tabText(index);
    if (editor->isChanged()) {
        if (!title.contains("*")) {
            title.append("*");
        }
    }
    else {
        if (title.at(title.length() - 1) == '*') {
            title.remove(title.length() - 1, 1);
        }
    }
    this->setTabText(index, title);
}

void XMLCoreWindow::updateCurrentDocumentChangeState()
{
    updateDocumentChangeState(this->currentIndex());
}

void XMLCoreWindow::updateAllDocumentsChangeState()
{
    for (int i = 0; i < this->count(); ++i) {
        updateDocumentChangeState(i);
    }
}

void XMLCoreWindow::checkSyntax()
{
    XMLCoreEditor *editor = dynamic_cast<XMLCoreEditor *>(this->currentWidget());
    if (!editor) {
        return;
    }

    editor->clearErrorLineHighlight();

    if (editor->currentIndex() != EditorIndex_Text) {
        return;
    }

    QByteArray data = editor->getPlainText();

    QString error;
    int line = -1, column = -1;
    if (!XMLSyntaxChecker::check(data, &error, &line, &column)) {
        if (line >= 0) {
            editor->setErrorLineHighlight(line);
        }

        Issues issues;
        issues.type = IssuesType_Error;
        issues.file_name = editor->getFilePath();
        issues.line = line;
        issues.column = column;
        issues.text = error;
        issues.line_text = editor->getLineText(line - 1);
        issues.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");

        domy_msg->appendIssues(issues);
    }
}

void XMLCoreWindow::setDocumentActionEnable(bool enable)
{
    m_act_ctrl(ActKey_CloseFile, enable);
    m_act_ctrl(ActKey_CloseAllFiles, enable);
    m_act_ctrl(ActKey_SaveOne, enable);
    m_act_ctrl(ActKey_SaveAs, enable);
    m_act_ctrl(ActKey_SaveAll, enable);
    m_act_ctrl(ActKey_Cut, enable);
    m_act_ctrl(ActKey_Paste, enable);
    m_act_ctrl(ActKey_Shortcut, enable);
    m_act_ctrl(ActKey_ShortcutComment, enable);
    m_act_ctrl(ActKey_ShortcutApostrophe, enable);
    m_act_ctrl(ActKey_ShortcutQuotation, enable);
    m_act_ctrl(ActKey_ShortcutAngleBracket, enable);
    m_act_ctrl(ActKey_ShortcutParen, enable);
    m_act_ctrl(ActKey_ShortcutBracket, enable);
    m_act_ctrl(ActKey_ShortcutBrace, enable);
}

} // namespace Domy
